package dormouse.fx.renderers;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.input.MouseEvent;
import javafx.util.Callback;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.log4j.Logger;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;

import dormouse.fx.FileObjectRow;
import dormouse.vfs.FileUtil;
import dormouse.vfs.PathUtil;
import dormouse.vfs.event.FileSelectionEvent;
import dormouse.vfs.event.FileSelectionEventMixin;
import dormouse.vfs.event.IFileSelectionListener;
import dormouse.vfs.event.ILocationChangeEvent;
import dormouse.vfs.ui.renderers.IFileListRenderer;

/**
 * This class is responsible or rendering a list of files in a table.
 */
public class FileTable extends TableView<FileObjectRow> implements
		IFileListRenderer<Node> {

	/** The class logger. */
	private static final Logger log = Logger.getLogger(FileTable.class);

	/** Formatter responsible for formatting file dates. */
	private SimpleDateFormat formatter = new SimpleDateFormat();

	/** The name of the renderer. */
	private static final String NAME = "File Table";

	/** The mixin used to add file selection event support to the table. */
	private FileSelectionEventMixin eventMixin = new FileSelectionEventMixin();

	/**
	 * Constructor.
	 */
	public FileTable() {
		init();
	}

	private void init() {
		TableColumn<FileObjectRow, String> fileName = new TableColumn<>("Name");
		fileName.setCellValueFactory(new Callback<CellDataFeatures<FileObjectRow, String>, ObservableValue<String>>() {

			@Override
			public ObservableValue<String> call(
					CellDataFeatures<FileObjectRow, String> features) {
				return features.getValue().name();
			}

		});

		TableColumn<FileObjectRow, String> fileDate = new TableColumn<>(
				"Modified Date");
		fileDate.setCellValueFactory(new Callback<CellDataFeatures<FileObjectRow, String>, ObservableValue<String>>() {

			@Override
			public ObservableValue<String> call(
					CellDataFeatures<FileObjectRow, String> features) {

				Date date = null;
				String formattedDate = null;

				date = new Date(features.getValue().lastModifiedTime()
						.longValue());
				formattedDate = formatter.format(date);

				return new SimpleStringProperty(formattedDate);

			}

		});

		TableColumn<FileObjectRow, Number> fileSize = new TableColumn<>("Size");
		fileSize.setCellValueFactory(new Callback<CellDataFeatures<FileObjectRow, Number>, ObservableValue<Number>>() {
			@Override
			public ObservableValue<Number> call(
					CellDataFeatures<FileObjectRow, Number> features) {

				return features.getValue().size();

			}

		});

		TableColumn<FileObjectRow, String> fileType = new TableColumn<>("Kind");
		fileType.setCellValueFactory(new Callback<CellDataFeatures<FileObjectRow, String>, ObservableValue<String>>() {

			@Override
			public ObservableValue<String> call(
					CellDataFeatures<FileObjectRow, String> features) {

				SimpleStringProperty type = new SimpleStringProperty();
				if (features.getValue().hidden().get()) {
					type.set("Hidden");
				} else if (features.getValue().isDirectory().get()) {
					type.set("Directory");
				} else {
					type.set(features.getValue().getFile().getName()
							.getExtension());
				}
				return type;
			}

		});

		getColumns().addAll(fileName, fileDate, fileSize, fileType);

		addEventHandler(MouseEvent.MOUSE_RELEASED,
				new EventHandler<MouseEvent>() {

					@Override
					public void handle(MouseEvent arg0) {
						FileObjectRow selectedRow = getSelectionModel()
								.getSelectedItem();
						FileObject selectedFile = selectedRow.getFile();
						notifyFileSelectionListeners(selectedFile);

					}

				});

	}

	@Override
	public void render(FileObject dir, Predicate<FileObject> predicate) {
		try {
			FileObject[] children = dir.getChildren();
			List<FileObject> filteredList = new ArrayList<FileObject>();
			for (FileObject file : children) {
				if (predicate.apply(file)) {
					filteredList.add(file);
				}
			}
			render(filteredList, null);
		} catch (FileSystemException e) {
			log.error(e.getMessage(), e);
		}

	}

	@Override
	public void render(String dir, Predicate<FileObject> predicate) {
		FileObject dirFileObject = null;
		try {
			dirFileObject = PathUtil.resolveFile(dir);
			render(dirFileObject, predicate);
		} catch (FileSystemException e) {
			log.error(e.getMessage(), e);
		}

	}

	@Override
	public void render(List<FileObject> fileList,
			Predicate<FileObject> predicate) {

		List<FileObject> nuFileList = fileList;
		if (predicate != null) {
			nuFileList = (List<FileObject>) Collections2.filter(fileList,
					predicate);
		}
		getItems().clear();
		getItems().addAll(convertFileList(nuFileList));

	}

	/**
	 * This method converts a list of file objects into FileObjectRow's (the
	 * underlying model used in the table).
	 * 
	 * @param fileList
	 *            the list of file objects.
	 * @return a list of FileObjectRows
	 */
	private List<FileObjectRow> convertFileList(List<FileObject> fileList) {
		List<FileObjectRow> nuList = new ArrayList<>();

		for (FileObject currFile : fileList) {
			nuList.add(new FileObjectRow(currFile));
		}
		return nuList;
	}

	@Override
	public Node getComponent() {
		return this;
	}

	@Override
	public void notifyFileSelectionListeners(FileObject selectedFile) {
		eventMixin.notify(new FileSelectionEvent(selectedFile));
	}

	@Override
	public void addFileSelectionListener(IFileSelectionListener listener) {
		eventMixin.addFileSelectionListener(listener);
	}

	@Override
	public void removeFileSelectionListener(IFileSelectionListener listener) {
		eventMixin.removeFileSelectionListener(listener);
	}

	@Override
	public void removeAllFileSelectionListeners() {
		eventMixin.removeAll();
	}

	@Override
	public String getName() {
		return NAME;
	}

	@Override
	public void locationChanged(ILocationChangeEvent event) {
		FileObject dir = event.getCurrentLocation();
		try {
			List<FileObject> fileList = FileUtil.createFileList(dir
					.getChildren());
			render(fileList, null);
		} catch (FileSystemException e) {
			log.error(e.getMessage(), e);
		}

	}

}
